package de.androidbuch.amando.services.impl;

import java.util.List;

import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import de.androidbuch.amando.common.AmandoSmsUtil;
import de.androidbuch.amando.common.GeoMarkierung;
import de.androidbuch.amando.common.GpsDataParcelable;
import de.androidbuch.amando.kontakt.GeoPosition;
import de.androidbuch.amando.kontakt.TelefonnummernHelfer;
import de.androidbuch.amando.services.IGeoPositionsServiceRemote;
import de.androidbuch.amando.services.IServiceCallback;

/**
 * Remote Service-Variante des @see{GeoPositionsService}.
 * 
 * @author Arno Becker, 2010 visionera gmbh
 */
public class GeoPositionsServiceRemoteImpl extends Service {

  /** Tag für die LogCat. */
  private static final String TAG =
    "GeoPositionsServiceRemoteImpl";

  /** Liste mit den Callbacks des Remote-Service. */
  private final RemoteCallbackList<IServiceCallback> 
    mCallbacks = new RemoteCallbackList<IServiceCallback>();

  /**
   * Minimaler Zeitraum zwischen zwei Abfragen der aktuellen
   * Ortsposition vom LocationManager.
   */
  private static int sMinZeit = 5000; // 5 Sekunden
  
  /**
   * Minimale Ortsveraenderung, ab der der Locationmanager
   * eine neue Ortsposition abfragt.
   */
  private static int sMinDistanz = 10; // 10 Meter

  /** Letzte bekannte eigene Geoposition. */
  private GpsDataParcelable mGpsData;

  /**
   * Bei Positionsänderung die Position an den Amando-Server
   * schicken.
   */
  private boolean mPositionNachverfolgen;

  /** Mobilnummern der SMS-Empfänger. */
  private List<String> mEmpfaengerMobilnummern;

  /**
   * Das Stichwort des Senders, welches per SMS an den
   * Empfaenger geschickt wird.
   */
  private String mStichwort;

  /**
   * Das Flag wird immer dann auf true gesetzt, wenn die
   * 'Position senden'- Schaltfläche gedrückt wurde. Sobald
   * der Location-Listener eine Position ermittelt hat, wird
   * die SMS mit den Positionsdaten verschickt und, falls
   * die Position nachverfolgt werden soll, die Position an
   * den Server geschickt.
   */
  private boolean mSendePositionFlag;

  /** Binder für Zugriff auf diesen Service. */
  private NetzwerkServiceImpl.NetzwerkServiceBinder 
      mNetzwerkBinder;  
  
  /** LocationManager zur Positionermittlung. */
  private LocationManager mLocationManager;
  
  /** 
   * Listener lauscht auf Änderungen der eigenen Position. 
   */
  private LocationListener mLocationListener;

  /**
   * Implementierung eines LocationListener, um auf 
   * Ortsveränderungen reagieren zu können.
   * 
   * @author Arno Becker, 2010 visionera gmbh
   *
   */
  private class MyLocationListener implements
      LocationListener {
    @Override
    public void onLocationChanged(Location location) {
      Log.d(TAG, "MyLocationListener->" +
          "onLocationChanged(): entered...");
      if (location != null) {
        Log.d(TAG, "MyLocationListener->" +
            "onLocationChanged(): Längengrad: " + 
            location.getLongitude());
        Log.d(TAG, "MyLocationListener->" +
            "onLocationChanged(): Breitengrad: " +
            location.getLatitude());
        mGpsData = new GpsDataParcelable(location
            .getLongitude(),
            location.getLatitude(), location.getAltitude(),
            location.getTime());

        // letzte bekannte eigene Position an den
        // Amando-Server schicken, falls
        // die Clients (die Empfänger der SMS) die eigenen
        // Positionsänderungen
        // nachverfolgen dürfen:
        if (mPositionNachverfolgen) {
          sendeGeoPositionAnServer();
        }

        if (mSendePositionFlag) {
          // an jeden Empfänger eine SMS schicken
          if (mEmpfaengerMobilnummern != null) {
            sendeGeoPositionPerSms();
          }
          mSendePositionFlag = false;
        }
      }
    }

    @Override
    public void onProviderDisabled(String provider) {
    }

    @Override
    public void onProviderEnabled(String provider) {
    }

    @Override
    public void onStatusChanged(String provider,
        int status,
        Bundle extras) {
    }
  };

  /**
   * Baut eine Verbindung zum lokalen Service auf. Der
   * Service laeuft im gleichen Prozess wie diese Activity.
   * Daher wird er automatisch beendet, wenn der Prozess der
   * Activity beendet wird.
   */
  private ServiceConnection mNetzwerkServiceConnection = 
      new ServiceConnection() {
    // Wird aufgerufen, sobald die Verbindung zum
    // Netzwerkservice steht.
    @Override
    public void onServiceConnected(ComponentName className,
        IBinder binder) {
      Log.d(TAG, "netzwerkServiceConnection->" +
          "onServiceConnected(): Verbindung mit Service " +
          "steht...");
      mNetzwerkBinder = 
        (NetzwerkServiceImpl.NetzwerkServiceBinder) binder;
    }

    // Wird aufgerufen, sobald die Verbindung zum Service
    // unterbrochen wird.
    // Dies passiert nur, wenn der Prozess, der den Service
    // gestartet hat, stirbt.
    // Da dies ein lokaler Service ist, läuft er im selben
    // Prozess wie diese Activity.
    // Daher kann die Methode niemals aufgrufen werden und
    // muss nicht implementiert
    // werden.
    public void onServiceDisconnected(
        ComponentName className) {
      Log.d(TAG, "netzwerkServiceConnection->" +
          "onServiceDisconnected(): Verbindung mit " +
          "Service beendet...");
    }
  };

  /**
   * Klassen, die den Binder fuer den Zugriff von Clients
   * auf diesen Service definiert. Da dieser Service immer
   * im gleichen Prozess wie der Aufrufer laeuft, ist kein
   * IPC notwendig.
   */
  private final IGeoPositionsServiceRemote.Stub 
      mGpsBinder = new IGeoPositionsServiceRemote.Stub() {
    @Override
    public GpsDataParcelable getGpsData()
        throws RemoteException {
      return mGpsData;
    }

    @Override
    public void getGpsDataAsynchron()
        throws RemoteException {
      final int anzCallbacks = mCallbacks.beginBroadcast();
      Log.d(TAG, "IGeoPositionsServiceRemote->" +
          "getGpsDataAsynchron(): Anzahl der Callbacks " +
          "im Remote-Service: " + anzCallbacks);
      for (int i = 0; i < anzCallbacks; i++) {
        Log.d(TAG, "IGeoPositionsServiceRemote->" +
            "getGpsDataAsynchron(): Callback-Nr.: " + i);
        try {
          mCallbacks.getBroadcastItem(i).aktuellePosition(
              mGpsData);
        } catch (RemoteException e) {
          e.printStackTrace();
        }
      }
      mCallbacks.finishBroadcast();
    }

    @Override
    public void sendeGeoPosition(
        List<String> empfaengerMobilnummern,
        String stichwort, boolean positionAktualisieren)
        throws RemoteException {
      Log.d(TAG, "sendeGeoPosition(): Stichwort: "
          + stichwort);

      mEmpfaengerMobilnummern = empfaengerMobilnummern;
      mStichwort = stichwort;
      mPositionNachverfolgen = positionAktualisieren;
      mSendePositionFlag = true;

      // FIXME ARB Position schicken, wenn sie nicht aelter
      // als x sekunden ist
    }

    @Override
    public void registriereCallback(
        IServiceCallback callback) throws RemoteException {
      Log.d(TAG, "registriereCallback(): entered...");
      if (callback != null) {
        mCallbacks.register(callback);
      }
    }

    @Override
    public void entferneCallback(IServiceCallback callback)
        throws RemoteException {
      Log.d(TAG, "entferneCallback(): entered...");
      if (callback != null) {
        mCallbacks.unregister(callback);
      }
    }
  };

  @Override
  public void onCreate() {
    Log.d(TAG, "onCreate(): entered...");
    
    Log.d(TAG, "onCreate(): PID: " + Process.myPid());
    Log.d(TAG, "onCreate(): TID: " + Process.myTid());
    Log.d(TAG, "onCreate(): UID: " + Process.myUid());
    
    // verbinde mit Service zum Uebertragen der Geoposition:
    Log.d(TAG, "onCreate(): Verbindung zum " +
        "Netzwerkservice aufbauen...");
    final Intent netzwerkIntent = new Intent(this,
        NetzwerkServiceImpl.class);
    bindService(netzwerkIntent, mNetzwerkServiceConnection,
        Context.BIND_AUTO_CREATE);

    mLocationManager = (LocationManager) 
        getSystemService(Context.LOCATION_SERVICE);
    mLocationListener = new MyLocationListener();
    mLocationManager.requestLocationUpdates(
        LocationManager.GPS_PROVIDER,
        sMinZeit,
        sMinDistanz,
        mLocationListener);
  }

  @Override
  public void onDestroy() {
    this.unbindService(mNetzwerkServiceConnection);
    this.stopService(new Intent(this,
        NetzwerkServiceImpl.class));
    Log.d(TAG, "onDestroy(): Service beenden...");
  }

  @Override
  public IBinder onBind(Intent intent) {
    Log.d(TAG, "onBind(): entered...");
    return mGpsBinder;
  }

  /** 
   * Schickt eine SMS an den Empfänger einer Geoposition. 
   */
  private void sendeGeoPositionPerSms() {
    Log.d(TAG, "sendeGeoPositionPerSms(): Schicke eine " +
        "SMS an die Empfänger der Geoposition");

    final GeoMarkierung geoMarkierung =
        new GeoMarkierung(mStichwort, mGpsData.toGpsData());

    final String eigeneMobilnummer = 
      ermittleEigeneMobilnummer();
    // Emulator:
    if ("15555218135".equals(eigeneMobilnummer)) { 
      Log.d(TAG, "sendeGeoPositionPerSms(): Android-Gerät" +
          " ist: Emulator");
      AmandoSmsUtil.sendeSms(mEmpfaengerMobilnummern,
          geoMarkierung,
          mPositionNachverfolgen,
          AmandoSmsUtil.TYP_TEXT_SMS);
    // echtes Gerät:
    } else { 
      Log.d(TAG, "sendeGeoPositionPerSms(): " +
          "Android-Gerät ist: Endgerät");
      AmandoSmsUtil.sendeSms(mEmpfaengerMobilnummern,
          geoMarkierung,
          mPositionNachverfolgen,
          AmandoSmsUtil.TYP_DATEN_SMS);
    }
  }

  /** Schickt die aktuelle Position an den Amando-Server. */
  private void sendeGeoPositionAnServer() {
    Log.d(TAG, "sendeGeoPositionAnServer(): schicke die " +
        "aktuelle Position an den Amando-Server");
    final String eigeneMobilnummer = 
      ermittleEigeneMobilnummer();
    final GeoPosition position =
        new GeoPosition(mGpsData.toGpsData(),
        eigeneMobilnummer);
    ((NetzwerkServiceImpl.NetzwerkServiceBinder)
        mNetzwerkBinder).sendeEigenePosition(position);
  }

  /**
   * Liefert die eigene Mobilnummer aus den 
   * Systemeinstellungen.
   * @return eigene Mobilnummer
   */
  private String ermittleEigeneMobilnummer() {
    final String eigeneMobilnummer =
        TelefonnummernHelfer
        .ermittleEigeneMobilnummer(this);
    return TelefonnummernHelfer
        .bereinigeTelefonnummer(eigeneMobilnummer);
  }

}
